#define FUSE_USE_VERSION 310

#include <stdint.h>
#include <sys/mman.h>

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <fuse.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "bubblefs.h"
#include "cwalk.h"

// optional options, configure them as you please
struct options {
  const char *filename;
  bool show_help;
};

static struct options options;
static struct bubblefs_superblock sb;
static int fd;
static char* fs;

#define OPTION(t, p)                                                           \
  { t, offsetof(struct options, p), 1 }

static const struct fuse_opt option_spec[] = {
    OPTION("--backing-file=%s", filename), OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END};


// help functions
uint64_t min(uint64_t src1, uint64_t src2) {
  if(src1 < src2) {
    return src1;
  }else {
    return src2;
  }
}

void read_block(int bno, char* buf) {
  memcpy(buf, fs + (bno * BLOCKSIZE), BLOCKSIZE);
}

void write_block(int bno, char* buf) {
  memcpy(fs + (bno * BLOCKSIZE), buf, BLOCKSIZE);
}

void read_supper_block(struct bubblefs_superblock* res) {
  char buf[BLOCKSIZE];
  read_block(0, buf);

  *res = *(struct bubblefs_superblock*)buf;
}

// read an inode using inode_idx and return result to *inode
void read_inode(uint64_t inode_idx, struct bubblefs_inode* inode) {
  char buf[BLOCKSIZE];
  uint64_t bno = sb.inodes_offset + (inode_idx / INODES_PER_BLOCK);
  read_block(bno, buf);

  struct bubblefs_inode* inodes = (struct bubblefs_inode*)buf;
  *inode = inodes[inode_idx % INODES_PER_BLOCK];
}

// write an inode using inode_idx and *inode
void write_inode(uint64_t inode_idx, struct bubblefs_inode* inode) {
  char buf[BLOCKSIZE];
  uint64_t bno = sb.inodes_offset + (inode_idx / INODES_PER_BLOCK);
  read_block(bno, buf);

  struct bubblefs_inode* inodes = (struct bubblefs_inode*)buf;
  inodes[inode_idx % INODES_PER_BLOCK] = *inode;
  write_block(bno, buf);
}

// int finddir(uint64_t inum, char* name, int name_len, struct dirnode* res) {
//   struct bubblefs_inode inode;
//   read_inode(inum, &inode);

//   assert(inode.used);

//   size_t size = inode.size;
//   size_t nums = size / sizeof(struct dirnode);
//   size_t offset = 0;

//   for(size_t i = 0; i < nums;) {
//     uint64_t block_no = ((i + offset) / DIR_PER_BLOCK) % 16;
//     char buf[BLOCKSIZE];
//     if(inode.refs[block_no].btype == DATABLOCK) {
//       read_block(inode.refs[block_no].offset, buf);
//     }else if(inode.refs[block_no].btype == INODE) {
//       // indirect inode
//       assert(block_no == 15);
//       read_inode(inode.refs[block_no].offset, &inode);
//       offset += DIR_PER_BLOCK;
//       continue;
//     }
//     size_t count = DIR_PER_BLOCK;
//     if(i + DIR_PER_BLOCK >= nums) {
//       count = nums - i;
//     }
//     for(size_t j = 0; j < count; j++) {
//       if(strncmp(((struct dirnode*)buf)[j].name, name, name_len) == 0) {
//         *res = ((struct dirnode*)buf)[j];
//         return 0;
//       }
//     }
//     i+=count;
//   }

//   return -ENOENT;

// }

// look up a directory(using it's inode) and find a entry which has the same name with the file we want
int finddir(uint64_t inum, char* name, int name_len, struct dirnode* res) {
  struct bubblefs_inode inode;
  read_inode(inum, &inode);
  char buf[BLOCKSIZE];

  assert(inode.used);

  for(int i=0; i<15; i++) {
    if(inode.refs[i].btype == DATABLOCK) {
      read_block(inode.refs[i].offset, buf);

      struct dirnode* dirs = (struct dirnode*)buf;
      for(int j=0; j<DIR_PER_BLOCK; j++) {
        if((strncmp(dirs[j].name, name, name_len) == 0) && (strlen(dirs[j].name) == name_len)) {
          *res = dirs[j];
          return 0;
        }
      }
    }
  }

  return -ENOENT;

}

// allocate an inode
int alloci(const char *name, mode_t mode, struct bubblefs_inode* res, uint64_t* inum) {
  char buf[BLOCKSIZE];

  for(int i=0; i<sb.n_inodes; i+=INODES_PER_BLOCK) {
    uint64_t bno = sb.inodes_offset + (i / INODES_PER_BLOCK);
    read_block(bno, buf);

    int number = INODES_PER_BLOCK;
    if((sb.n_inodes - i) < INODES_PER_BLOCK) {
      number = sb.n_inodes - i;
    }

    for(int j=0; j<number; j++) {
      struct bubblefs_inode* inode = ((struct bubblefs_inode*)buf) + j;
      if(inode->used == 0) {
        // found 
        strcpy(inode->name, name);
        inode->file_stat.st_nlink = 1;
        inode->file_stat.st_size = 0;
        inode->file_stat.st_mode = mode;
        inode->size = 0;
        inode->used = 1;
        memset(inode->refs, 0, sizeof(inode->refs));
        write_inode(i + j, inode);
        *inum = i + j;
        *res = *inode;
        return 0;
      }
    }
  }
  return -ENOSPC;
}

// free an inode
void freei(uint64_t inum) {
  char buf[BLOCKSIZE];
  uint64_t bno = sb.inodes_offset + (inum / INODES_PER_BLOCK);
  read_block(bno, buf);

  struct bubblefs_inode* inodes = (struct bubblefs_inode*)buf;
  memset(&inodes[inum % INODES_PER_BLOCK], 0, sizeof(struct bubblefs_inode));
  write_block(bno, buf);
}

// alloc a zero disk data block
int alloc_block(uint64_t* res) {
  char buf[BLOCKSIZE];
  for(int i=0; i<(sb.size / BLOCKSIZE); i+=BLOCKSIZE*8) {
    uint64_t bno = sb.free_map_offset + (i / BLOCKSIZE*8);
    read_block(bno, buf);

    int count = BLOCKSIZE*8;
    if((sb.size / BLOCKSIZE - i) < BLOCKSIZE*8) {
      count = sb.size / BLOCKSIZE - i;
    }
    for(int j=0; j<count; j++) {
      uint8_t mask = 1 << (j % 8);
      uint8_t* free_map = (uint8_t*)buf;

      if((free_map[j / 8] & mask) == 0) {
        free_map[j / 8] |= mask;
        write_block(bno, buf);

        // free the new allocated block to zero
        read_block(i + j, buf);
        memset(buf, 0, BLOCKSIZE);
        write_block(i + j, buf);

        *res = i + j;
        return 0;
      }
    }
  }
  return -ENOSPC;
}

// free a disk data block
void free_block(uint64_t num) {
  char buf[BLOCKSIZE];
  uint64_t bno = sb.free_map_offset + (num / BLOCKSIZE*8);
  read_block(bno, buf);

  uint8_t mask = 1 << (num % 8);
  uint8_t* free_map = (uint8_t*)buf;

  free_map[num / 8] = free_map[num / 8] & ~mask;
  write_block(bno, buf);
}

// given a path like '/root/test', return the inode num and content of dir `test`
int findi(const char *path, struct bubblefs_inode* res, uint64_t* inum) {
  char dir_name[256] = {0};
  int dir_name_idx = 0;
  int state = 0;
  int len = strlen(path);
  struct dirnode dir;
  if(*path != '/') {
    return -1;
  }

  path += 1;
  len -= 1;
  uint64_t rooti = 0;

  for(int i=0; i<len; i++) {
    if(path[i] != '/') {
      dir_name[dir_name_idx++] = path[i];
    }else {
      // '/' is found 
      state = finddir(rooti, dir_name, dir_name_idx, &dir);
      if(state) {
        return state;
      }
      rooti = dir.inum;
      dir_name_idx = 0;
    }
  }
  if(dir_name_idx != 0) {
    state = finddir(rooti, dir_name, dir_name_idx, &dir);
    if(state) {
      return state;
    }
    rooti = dir.inum;
  }
  read_inode(rooti, res);
  *inum = rooti;
  return state;
}

int create_file_or_dir(const char *path, mode_t perms) {
  // something like `mkdir /abc/1` or `mkdir /1` or `touch /abc/1` or `touch /1`
  char name[256];
  char new_path[256];
  char buf[BLOCKSIZE];
  int len = strlen(path);
  struct bubblefs_inode inode;
  uint64_t inum;
  // find last index '/' appears 
  int idx = 0;
  for(int i=0; i<len; i++) {
    if(path[i] == '/') {
      idx = i;
    }
  }
  strcpy(name, path + idx + 1);
  strcpy(new_path, path);

  while(1) {
    if(new_path[len - 1] != '/') {
      new_path[len - 1] = 0;
      len--;
    }else {
      new_path[len - 1] = 0;
      len--;
      break;
    }
  }

  if(len == 0) {
    // prefix is /
    findi("/", &inode, &inum);
  }else {
    // prefix is like /abc
    findi(new_path, &inode, &inum);
  }

  struct bubblefs_inode new;
  uint64_t new_inum;
  int state = alloci(name, perms, &new, &new_inum);

  if(state) {
    return state;
  }

  int found = 0;
  for(int i=0; i<15; i++) {
    if(inode.refs[i].btype == EMPTY) {
      // create a block
      uint64_t tmp;
      state = alloc_block(&tmp);
      if(state) {
        return state;
      }
      inode.refs[i].btype = DATABLOCK;
      inode.refs[i].offset = tmp;
      write_inode(inum, &inode);
    }
    if(inode.refs[i].btype == DATABLOCK) {
      uint64_t bno = inode.refs[i].offset;
      read_block(bno, buf);

      struct dirnode* dirs = (struct dirnode*)buf;
      for(int j=0; j<DIR_PER_BLOCK; j++) {
        if(dirs[j].inum == 0) {
          dirs[j].inum = new_inum;
          strcpy(dirs[j].name, name);
          found = 1;
          write_block(bno, buf);
          break;
        }
      }
    }
    if(found) {
      break;
    }
  }
  if(!found) {
    return -ENOSPC;
  }

  return 0;
}

// given an inode compute the block number of given content bias related to this inode
uint64_t mapi(struct bubblefs_inode* inode, uint64_t inum, uint64_t bias) {
  if(bias < 15) {
    if(inode->refs[bias].btype == EMPTY) {
      inode->refs[bias].btype = DATABLOCK;
      uint64_t bno;
      int state = alloc_block(&bno);
      if(state)
        return state;
      inode->refs[bias].offset = bno;
      write_inode(inum, inode);
    }
    return inode->refs[bias].offset;
  }
  bias -= 15;
  struct bubblefs_inode inode_next;

  if(inode->refs[15].btype == EMPTY) {
    inode->refs[15].btype = INODE;
    struct bubblefs_inode tmp;
    uint64_t tmp_idx;

    int state = alloci(inode->name, inode->file_stat.st_mode, &tmp, &tmp_idx);
    if(state) {
      return state;
    }
    inode->refs[15].offset = tmp_idx;
    write_inode(inum, inode);
  }

  read_inode(inode->refs[15].offset, &inode_next);
  uint64_t res = mapi(&inode_next, inum, bias);
  write_inode(inode->refs[15].offset, &inode_next);
  return res;
}

uint64_t readi(struct bubblefs_inode* inode, uint64_t inum, uint64_t offset, uint64_t n, void* dst) {
  uint64_t total_bytes = 0;
  uint64_t count = 0;
  char buf[BLOCKSIZE];

  if(inode->size < offset) {
    return -1;
  }

  if(offset + n > inode->size) {
    n = inode->size - offset;
  }

  for(; total_bytes < n; total_bytes += count, offset += count) {
    read_block(mapi(inode, inum, offset / BLOCKSIZE), buf);
    count = min(n - total_bytes, BLOCKSIZE - (offset % BLOCKSIZE));
    memcpy(dst, buf + (offset % BLOCKSIZE), count);
  }

  return total_bytes;
}

uint64_t writei(struct bubblefs_inode* inode, uint64_t inum, uint64_t offset, uint64_t n, const char* src) {
  uint64_t total_bytes = 0;
  uint64_t count = 0;
  char buf[BLOCKSIZE];

  if(inode->size < offset) {
    return -1;
  }

  for(; total_bytes < n; total_bytes += count, offset += count) {
    read_block(mapi(inode, inum, offset / BLOCKSIZE), buf);
    count = min(n - total_bytes, BLOCKSIZE - (offset % BLOCKSIZE));
    memcpy(buf + (offset % BLOCKSIZE), src, count);
    write_block(mapi(inode, inum, offset / BLOCKSIZE), buf);
  }

  // update size anyway to fit vim
  // if(offset > inode->size) {
    inode->size = offset;
    inode->file_stat.st_size = offset;
    write_inode(inum, inode);
  // }

  return total_bytes; 
}

// given an inode, rm all the datablock of this inode
void unlinki(struct bubblefs_inode* inode, uint64_t inum) {
  for(int i=0; i<15; i++) {
    if(inode->refs[i].btype == DATABLOCK) {
      inode->refs[i].btype = EMPTY;
      free_block(inode->refs[i].offset);
    }
  }
  struct bubblefs_inode inode_next;
  if(inode->refs[15].btype == INODE) {
    read_inode(inode->refs[15].offset, &inode_next);
    unlinki(&inode_next, inode->refs[15].offset);
    inode->refs[15].btype = EMPTY;
  }
  write_inode(inum, inode);
  freei(inum);
}

// TODO: Implement me
void *bubblefs_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
  (void)conn;
  cfg->kernel_cache = 1;
  return NULL;
}

// TODO: Implement me
int bubblefs_getattr(const char *path, struct stat *statbuf,
                     struct fuse_file_info *fi) {
  
  struct bubblefs_inode inode;
  uint64_t inum;
  int state = findi(path, &inode, &inum);
  if(!state)
    *statbuf = inode.file_stat;
  return state;
}

// TODO: Implement me
int bubblefs_mkdir(const char *path, mode_t perms) {
  // something like `mkdir /abc/1` or `mkdir /1`
  int state = create_file_or_dir(path, perms | S_IFDIR);

  return state;
}

// TODO: Implement me
int bubblefs_unlink(const char *path) {
  // something like `rm /test/a.txt`
  char name[256];
  char new_path[256];
  char buf[BLOCKSIZE];
  int len = strlen(path);
  struct bubblefs_inode inode;
  uint64_t inum;

  int state = findi(path, &inode, &inum);
  if(state) {
    return state;
  }

  unlinki(&inode, inum);

  // find last index '/' appears 
  int idx = 0;
  for(int i=0; i<len; i++) {
    if(path[i] == '/') {
      idx = i;
    }
  }
  strcpy(name, path + idx + 1);
  strcpy(new_path, path);

  while(1) {
    if(new_path[len - 1] != '/') {
      new_path[len - 1] = 0;
      len--;
    }else {
      new_path[len - 1] = 0;
      len--;
      break;
    }
  }

  if(len == 0) {
    // prefix is /
    findi("/", &inode, &inum);
  }else {
    // prefix is like /abc
    findi(new_path, &inode, &inum);
  }
  int found = 0;

  for(int i=0; i<15; i++) {
    if(inode.refs[i].btype == DATABLOCK) {
      read_block(inode.refs[i].offset, buf);
      struct dirnode* dirs = (struct dirnode*)buf;
      for(int j=0; j<DIR_PER_BLOCK; j++) {
        if(dirs[j].inum != 0 && (strcmp(dirs[j].name, name) == 0)) {
          dirs[j].inum = 0;
          memset(dirs[j].name, 0, NAME_LEN);
          write_block(inode.refs[i].offset, buf);
          found = 1;
          break;
        }
      }
    }
    if(found)
      break;
  }

  return 0;
}

// TODO: Implement me
int bubblefs_rmdir(const char *path) {
  char name[256];
  char new_path[256];
  char buf[BLOCKSIZE];
  int len = strlen(path);
  struct bubblefs_inode inode;
  uint64_t inum;

  int state = findi(path, &inode, &inum);
  if(state) {
    return state;
  }
  
  for(int i=0; i<15; i++) {
    if(inode.refs[i].btype == DATABLOCK) {
      read_block(inode.refs[i].offset, buf);
      struct dirnode* dirs = (struct dirnode*)buf;
      for(int j=0; j<DIR_PER_BLOCK; j++) {
        if(dirs[j].inum != 0) {
          return -ENOTEMPTY;
        }
      }
      free_block(inode.refs[i].offset);
    }
  }
  freei(inum);

  // find last index '/' appears 
  int idx = 0;
  for(int i=0; i<len; i++) {
    if(path[i] == '/') {
      idx = i;
    }
  }
  strcpy(name, path + idx + 1);
  strcpy(new_path, path);

  while(1) {
    if(new_path[len - 1] != '/') {
      new_path[len - 1] = 0;
      len--;
    }else {
      new_path[len - 1] = 0;
      len--;
      break;
    }
  }

  if(len == 0) {
    // prefix is /
    findi("/", &inode, &inum);
  }else {
    // prefix is like /abc
    findi(new_path, &inode, &inum);
  }
  int found = 0;

  for(int i=0; i<15; i++) {
    if(inode.refs[i].btype == DATABLOCK) {
      read_block(inode.refs[i].offset, buf);
      struct dirnode* dirs = (struct dirnode*)buf;
      for(int j=0; j<DIR_PER_BLOCK; j++) {
        if(dirs[j].inum != 0 && (strcmp(dirs[j].name, name) == 0)) {
          dirs[j].inum = 0;
          memset(dirs[j].name, 0, NAME_LEN);
          write_block(inode.refs[i].offset, buf);
          found = 1;
          break;
        }
      }
    }
    if(found)
      break;
  }

  return 0;

}

int juedge_dir(const char* old_path, const char* new_path) {
  int len1 = strlen(old_path);
  int len2 = strlen(new_path);
  int index1 = len1 - 1;
  int index2 = len2 - 1;
  while (index1 >=0){
      if(old_path[index1] != '/'){
          break;
      }
      index1--;
  }
  while (index1 >=0){
      if(old_path[index1] == '/'){
          break;
      }
      index1--;
  }
  while(index2 >= 0){
      if(new_path[index2] != '/'){
          break;
      }
      index2--;
  }
  while(index2 >= 0){
      if(new_path[index2] == '/'){
          break;
      }
      index2--;
  }
  if(index1 == index2 && strncmp(old_path, new_path, index1) == 0){
      return 1;     // same prefix
  }
  

  if (len1 > len2) {
    if (strncmp(old_path, new_path, len2) == 0) {
      return -1;    // cannot move a dir to its parent dir
    }
    return 0;
  } else if (len1 < len2) {
    if (strncmp(old_path, new_path, len1) == 0) {
      return -1;    // cannot move a dir to its sub dir
    }
    return 0;
  } else {
    if (strcmp(old_path, new_path) == 0) {
      return 2;
    }
    return 0;
  }
}

// TODO: Implement me
int bubblefs_rename(const char *old_path, const char *new_path,
                    unsigned int flags) {
  // four cases:
  // rename /a/b /a/c
  // rename /a/b /c
  // rename /a/b /a/b/c or /a/b/c /a/b
  // rename /a/b /a/b
  struct bubblefs_inode inode;
  uint64_t inum;

  int res = juedge_dir(old_path, new_path);
  
  if (res == -1) {
      return EINVAL;
  } else if (res == 2) {
      return 0;
  } else {
      struct bubblefs_inode old_inode;
      struct bubblefs_inode new_inode;
      uint64_t old_inum;
      uint64_t new_inum;

      int state = findi(new_path, &new_inode, &new_inum);
      if(!state) {
        return EEXIST;
      }

      state = findi(old_path, &old_inode, &old_inum);
      if(state) {
        return state;
      }

      create_file_or_dir(new_path, old_inode.file_stat.st_mode | S_IFDIR);
      state = findi(new_path, &new_inode, &new_inum);

      // copy data
      new_inode.used = old_inode.used;
      new_inode.file_stat = old_inode.file_stat;
      new_inode.size = old_inode.size;
      memcpy(new_inode.refs, old_inode.refs, sizeof(old_inode.refs));
      write_inode(new_inum, &new_inode);

      // delete old file
      char name[256];
      char old_path_parent[256];
      char buf[BLOCKSIZE];
      int len = strlen(old_path);

      freei(old_inum);

      // find last index '/' appears 
      int idx = 0;
      for(int i=0; i<len; i++) {
        if(old_path[i] == '/') {
          idx = i;
        }
      }
      strcpy(name, old_path + idx + 1);
      strcpy(old_path_parent, old_path);

      while(1) {
        if(old_path_parent[len - 1] != '/') {
          old_path_parent[len - 1] = 0;
          len--;
        }else {
          old_path_parent[len - 1] = 0;
          len--;
          break;
        }
      }

      if(len == 0) {
        // prefix is /
        findi("/", &inode, &inum);
      }else {
        // prefix is like /abc
        findi(old_path_parent, &inode, &inum);
      }
      int found = 0;

      for(int i=0; i<15; i++) {
        if(inode.refs[i].btype == DATABLOCK) {
          read_block(inode.refs[i].offset, buf);
          struct dirnode* dirs = (struct dirnode*)buf;
          for(int j=0; j<DIR_PER_BLOCK; j++) {
            if(dirs[j].inum != 0 && strcmp(dirs[j].name, name) == 0) {
              dirs[j].inum = 0;
              memset(dirs[j].name, 0, NAME_LEN);
              write_block(inode.refs[i].offset, buf);
              found = 1;
              break;
            }
          }
        }
        if(found)
          break;
      }
      return 0;
  }
}

// TODO: Implement me
int bubblefs_chmod(const char *path, mode_t perms, struct fuse_file_info *fi) {
  return 0;
}

// TODO: Implement me
int bubblefs_chown(const char *path, uid_t user, gid_t group,
                   struct fuse_file_info *fi) {
  return 0;
}

// TODO: Implement me
int bubblefs_truncate(const char *path, off_t offset,
                      struct fuse_file_info *fi) {
  struct bubblefs_inode inode;
  uint64_t inum;

  int state = findi(path, &inode, &inum);

  if(state) {
      return state;
  }

  inode.size = offset;
  inode.file_stat.st_size = offset;
  write_inode(inum, &inode);

  char *buf = (char *)malloc(offset);
  memset(buf, 0, offset);
  writei(&inode, inum, 0, offset, buf);
  free(buf);

  return 0;
}

// TODO: Implement me
int bubblefs_open(const char *path, struct fuse_file_info *fi) {
  struct bubblefs_inode inode;
  uint64_t inum;

  int state = findi(path, &inode, &inum);

  if(state) {
      return state;
  }

  fi->fh = inum;
  
  return 0;
}

// TODO: Implement me
int bubblefs_read(const char *path, char *buf, size_t size, off_t offset,
                  struct fuse_file_info *fi) {
  struct bubblefs_inode inode;
  uint64_t inum;

  int state = findi(path, &inode, &inum);

  if(state) 
    return state;
  
  off_t res = readi(&inode, inum, offset, size, buf);
  if(res < 0) {
    return -ENOSR;
  }

  return res;
}
// TODO: Implement me
int bubblefs_write(const char *path, const char *buf, size_t size, off_t offset,
                   struct fuse_file_info *fi) {
  struct bubblefs_inode inode;
  uint64_t inum;

  int state = findi(path, &inode, &inum);

  if(state) 
    return state;
  
  off_t res = writei(&inode, inum, offset, size, buf);
  if(res < 0) {
    return -ENOSR;
  }

  return res;
}

// TODO: Implement me
int bubblefs_statfs(const char *path, struct statvfs *stbuf) { return 0; }

// TODO: Implement me
int bubblefs_flush(const char *path, struct fuse_file_info *fi) { return 0; }

// TODO: Implement me
int bubblefs_release(const char *path, struct fuse_file_info *fi) { 
  struct bubblefs_inode inode;
  uint64_t inum;

  int state = findi(path, &inode, &inum);

  if(state) 
    return state;

  fi->fh = 0;
  
  return 0;
}

// TODO: Implement me
int bubblefs_fsync(const char *path, int isdatasync,

                   struct fuse_file_info *fi) {
  return 0;
}

// TODO: Implement me
int bubblefs_readdir(const char *path, void *buf, fuse_fill_dir_t dir,
                     off_t offset, struct fuse_file_info *fi,
                     enum fuse_readdir_flags flags) {
  char buffer[BLOCKSIZE];
  struct bubblefs_inode inode;
  uint64_t inum;
  int state = findi(path, &inode, &inum);

  if(state) {
    return state;
  }

  dir(buf, ".", NULL, 0, 0);
	dir(buf, "..", NULL, 0, 0);

  for(int i=0; i<15; i++) {
    if(inode.refs[i].btype == DATABLOCK) {
      read_block(inode.refs[i].offset, buffer);

      struct dirnode* dirs = (struct dirnode*)buffer;
      for(int j=0; j<DIR_PER_BLOCK; j++) {
        if(dirs[j].inum != 0) {
          struct bubblefs_inode tmp;
          read_inode(dirs[j].inum, &tmp);
          dir(buf, tmp.name, &(tmp.file_stat), 0, 0);
        }
      }
    }
  }


  return 0;
}

// TODO: Implement me
int bubblefs_create(const char *path, mode_t perms, struct fuse_file_info *fi) {
  // something like `mkdir /abc/1` or `mkdir /1`
  int state = create_file_or_dir(path, perms | S_IFREG);

  return state;
}

// TODO: Implement me
int bubblefs_utimens(const char *path, const struct timespec tv[2],
                     struct fuse_file_info *fi) {
  return 0;
}

static const struct fuse_operations bubblefs_oper = {
    .init = bubblefs_init,
    .getattr = bubblefs_getattr,
    .mkdir = bubblefs_mkdir,
    .unlink = bubblefs_unlink,
    .rmdir = bubblefs_rmdir,
    .rename = bubblefs_rename,
    .chmod = bubblefs_chmod,
    .chown = bubblefs_chown,
    .truncate = bubblefs_truncate,
    .open = bubblefs_open,
    .read = bubblefs_read,
    .write = bubblefs_write,
    .statfs = bubblefs_statfs,
    .flush = bubblefs_flush,
    .release = bubblefs_release,
    .fsync = bubblefs_fsync,
    .readdir = bubblefs_readdir,
    .create = bubblefs_create,
    .utimens = bubblefs_utimens,
};

// TODO: Show help
static void show_help(const char *progname) {
  printf("usage: %s --backing-file=<filename> <mountpoint>\n\n", progname);
  printf("File-system specific options:\n"
         "    --backing-file=<s>          Name of the file that backs the "
         "filesystem"
         "\n");
}

int main(int argc, char *argv[]) {
  int ret;
  struct fuse_args args = FUSE_ARGS_INIT(argc, argv);

  /* Parse options */
  if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) {
    return 1;
  }

  /* When --help is specified, first print our own file-system
     specific help text, then signal fuse_main to show
     additional help (by adding `--help` to the options again)
     without usage: line (by setting argv[0] to the empty
     string) */
  if (options.show_help) {
    show_help(argv[0]);
    assert(fuse_opt_add_arg(&args, "--help") == 0);
    args.argv[0][0] = '\0';
  }

  if (options.filename == NULL) {
    fprintf(stderr, "No backing file provided\n");
    return 1;
  }

  fd = open(options.filename, O_RDWR);
  if (fd == -1) {
    perror("Failed to open the specified file");
    return 1;
  }

  fs = mmap(NULL, TOTALSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

  if (fs == NULL) {
    perror("Failed to mmap the specified file");
    return 1;
  }

  read_supper_block(&sb);

  if(sb.magic != MAGICNUM) {
    fprintf(stderr, "Invalid backing-file type\n");
    return 1;
  }

  ret = fuse_main(args.argc, args.argv, &bubblefs_oper, NULL);
  fuse_opt_free_args(&args);

  munmap(fs, TOTALSIZE);

  return ret;
}
